/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.common.util;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.text.StringCharacterIterator;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * 类型转换工具类
 *
 * @author vacoor
 */

public abstract class Castor {
    private static final String DATE_FORMAT_PLAIN = "yyyy-MM-dd HH:mm:ss.SSS";
    private static final String DATE_FORMAT_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
    private static final String DATE_FORMAT_ISO8601_Z = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
    private static final String DATE_FORMAT_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";

    /**
     * 将一个对象转换为字符串
     * 如果对象为null, 返回 null, 否则返回 {@link Object#toString()}
     *
     * @param obj
     * @return
     */
    public static String asString(final Object obj) {
        return obj == null ? null : obj.toString();
    }

    /**
     * 将一个对象转换为 Boolean
     * {@link #asString(Object)} 为
     * true|y|yes|on|1 的对象都将返回 true
     * false|n|no|off|0 的对象都返回 false
     *
     * @param obj
     * @return
     */
    public static Boolean asBoolean(final Object obj) {
        if (obj instanceof Boolean) {
            return (Boolean) obj;
        }

        if (obj instanceof Number) {
            int i = ((Number) obj).intValue();
            if (0 == i) {
                return false;
            }
            if (1 == i) {
                return true;
            }
        }

        String s = asString(obj);

        if (s == null || 0 == s.length()) {
            return null;
        }

        if ("true".equalsIgnoreCase(s) || "y".equalsIgnoreCase(s) || "yes".equalsIgnoreCase(s) || "on".equalsIgnoreCase(s) || "1".equalsIgnoreCase(s)) {
            return true;
        }

        if ("false".equalsIgnoreCase(s) || "n".equalsIgnoreCase(s) || "no".equalsIgnoreCase(s) || "off".equalsIgnoreCase(s) || "0".equalsIgnoreCase(s)) {
            return false;
        }

        throw newClassCastException(obj, Boolean.class);
    }

    public static Byte asByte(Object obj) {
        return asNumber(obj, Byte.class);
    }

    public static Short asShort(Object obj) {
        return asNumber(obj, Short.class);
    }

    public static Integer asInt(Object obj) {
        return asNumber(obj, Integer.class);
    }

    public static Long asLong(Object obj) {
        return asNumber(obj, Long.class);
    }

    public static Float asFloat(Object obj) {
        return asNumber(obj, Float.class);
    }

    public static Double asDouble(Object obj) {
        return asNumber(obj, Double.class);
    }

    /**
     * 将一个对象转换为 Number 的子类
     *
     * @param obj
     * @param targetClass
     * @param <N>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <N extends Number> N asNumber(Object obj, Class<N> targetClass) {
        if (obj == null) {
            return null;
        }

        // fix "true" | "false"
        try {
            obj = asBoolean(obj);
        } catch (ClassCastException ignore) {
        }

        String text;
        if (obj instanceof Boolean) {
            text = (Boolean) obj ? "1" : "0";
        } else if (obj instanceof Character) {
            text = Integer.valueOf(((Character) obj).charValue()).toString();
        } else {
            text = asString(obj);
            if (text == null || 0 == text.length()) {
                return null;
            }
        }

        try {
            if (Byte.class == targetClass || byte.class == targetClass) {
                return (N) Byte.decode(text);
            }
            if (Short.class == targetClass || short.class == targetClass) {
                return (N) Short.decode(text);
            }
            if (Integer.class == targetClass || int.class == targetClass) {
                return (N) Integer.decode(text);
            }
            if (Long.class == targetClass || long.class == targetClass) {
                return (N) Long.decode(text);
            }
            if (Float.class == targetClass || float.class == targetClass) {
                return (N) Float.valueOf(text);
            }
            if (Double.class == targetClass || double.class == targetClass) {
                return (N) Double.valueOf(text);
            }
            if (BigInteger.class == targetClass) {
                return (N) new BigInteger(text);
            }
            if (BigDecimal.class == targetClass || Number.class == targetClass) {
                return (N) new BigDecimal(text);
            }
        } catch (NumberFormatException e) {
        }

        throw newClassCastException(obj, Number.class);
    }

    /**
     * 该方法提供类似 JavaScript 的parse* 的从10进制字符串中解析数值
     * 两种模式: 严格和宽松
     * <p/>
     * 宽松模式: parseNumber("12a3", false) --> 12, 尽可能多的匹配数值
     * <p/>
     *
     * @param text
     * @return
     */
    public static Number parseNumber(String text) {
        // 全部转换为大写 e --> E
        StringCharacterIterator it = new StringCharacterIterator(text.trim().toUpperCase(Locale.ENGLISH));
        long number = 0;
        StringBuilder buffer = null;
        boolean negative = false;

        // 提取整数循环
        longLoop:
        while (it.getIndex() < it.getEndIndex()) {
            char c = it.current();
            switch (c) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    number *= 10;
                    number += (c - '0');
                    it.next();
                    break;
                case '-':
                case '+':
                    if (0 != number) {
                        break longLoop;
                    }
                    negative = '-' == c;
                    it.next();
                    break;
                // double 标志
                case '.':
                case 'E': // 'e', 'E'
                    buffer = new StringBuilder(16);
                    buffer.append(negative ? '-' : "").append(number).append(c);
                    it.next();
                    break longLoop;
                default:
                    it.next();
                    break longLoop;
            }
        }

        if (buffer == null) {    // 只有整数部分
            return negative ? -1 * number : number;
        }

        final boolean dot = '.' == buffer.charAt(buffer.length() - 1); // '.', 'e', 'E'
        doubleLoop:
        while (it.getIndex() < it.getEndIndex()) {
            char ch = it.current();

            switch (ch) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    buffer.append(ch);
                    it.next();
                    break;
                case '+':
                case '-':
                    // 只允许 E 后面出现 + / -
                    if ('E' != buffer.charAt(buffer.length() - 1)) {
                        break doubleLoop;
                    }
                    buffer.append('-' == ch ? ch : "");
                    it.next();
                    break;
                case '.':
                case 'E':
                    // 如果 . 前面应出现过 . 或 E
                    if (('.' == ch && dot) || ('E' == ch && !dot)) {
                        // ------
                        break doubleLoop;
                    }
                    buffer.append(ch);
                    it.next();
                default:
                    break doubleLoop;
            }
        }
        if ('E' == buffer.charAt(buffer.length() - 1)) {
            buffer.append('0');
        }
        return new Double(buffer.toString());
    }

    public static Date asDate(String text, String format) {
        try {
            return new SimpleDateFormat(format).parse(text);
        } catch (ParseException e) {
            throw newClassCastException(text, Date.class);
        }
    }


    /**
     * 日期字符串支持如下
     * yyyy-MM-dd
     * yyyy-MM-dd HH:mm:ss
     * yyyy-MM-dd HH:mm:ss.SSS
     * <p/>
     * ISO8601
     * yyyy-MM-dd'T'HH:mm:ssZ
     * yyyy-MM-dd'T'HH:mm:ss'Z'
     * yyyy-MM-dd'T'HH:mm:ss.SSSZ
     * yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
     * yyyy-MM-dd'T'HH:mm:ss.SSS'Z' (时区格式:兼容+/-hh,+/-hh:mm,+/-hhmm)
     * RFC1123
     * EEE, dd MMM yyyy HH:mm:ss zzz
     *
     * @param obj
     * @return
     */
    public static Date asDate(final Object obj) {
        if (obj == null || obj instanceof Date) {
            return (Date) obj;
        }

        if (obj instanceof Calendar) {
            return ((Calendar) obj).getTime();
        }

        if (obj instanceof java.sql.Date) {
            return new Date(((java.sql.Date) obj).getTime());
        }

        if (obj instanceof java.sql.Time) {
            return new Date(((java.sql.Time) obj).getTime());
        }

        if (obj instanceof java.sql.Timestamp) {
            return new Date(((java.sql.Timestamp) obj).getTime());
        }


        long offset = 0;

        if (obj instanceof String) {
            String text = (String) obj;
            int len = text.length();

            if (0 == len) {
                return null;
            }

            // 优先尝试解析日期字符串
            if (-1 < text.indexOf('-')) {
                if (len <= DATE_FORMAT_PLAIN.length() && -1 == text.indexOf('T')) {
                    // yyyy-MM-dd HH:mm:ss
                    char c = text.charAt(len - 3);

                    if (':' == c) {
                        // 缺失毫秒
                        text += ".000";
                    } else if ('-' == c) {
                        // 缺失 time
                        text += " 00:00:00.000";
                    }

                    try {
                        return new SimpleDateFormat(DATE_FORMAT_PLAIN).parse(text);
                    } catch (ParseException ignore) {
                        // ignore
                    }
                } else if (len <= DATE_FORMAT_ISO8601.length() && -1 < text.indexOf('T')) {
                    // yyyy-MM-dd'T'HH:mm:ss.SSS'Z'/yyyy-MM-dd'T'HH:mm:ss.SSS+0800
                    final char c = text.charAt(len - 1);

                    if ('Z' == c) {
                        // Z 结尾 ISO8601, 应该是2012-12-12T00:00:00.000Z

                        // 缺失毫秒, eg: 2012-12-12T00:00:00Z
                        if (':' == text.charAt(len - 4)) {
                            StringBuilder buf = new StringBuilder(text);
                            buf.insert(len - 1, ".000");
                            text = buf.toString();
                        }

                        try {
                            return new SimpleDateFormat(DATE_FORMAT_ISO8601_Z).parse(text);
                        } catch (ParseException ignore) {
                        }
                    } else {
                        // 应该为具体 TimeZone 2012-12-12T00:00:00.000+0800
                        if (hasTimeZone(text)) {
                            char ch = text.charAt(len - 3);

                            if (':' == ch) {
                                // +hh:mm 删除时区中可选的:
                                text = new StringBuilder(text).deleteCharAt(len - 3).toString();
                            } else if ('+' == ch || '-' == ch) {
                                // +hh -hh 填充缺失的分钟
                                text += "00";
                            }

                            if (Character.isDigit(text.charAt(text.length() - 9))) {
                                // 2012-02-07T12:00:00+0800 缺失毫秒
                                text = new StringBuilder(text).insert(text.length() - 5, ".000").toString();
                            }

                            try {
                                return new SimpleDateFormat(DATE_FORMAT_ISO8601).parse(text);
                            } catch (ParseException ignore) {
                            }
                        } else {
                            // 没有时区
                            // 2014-02-12T02:00:00
                            StringBuilder sb = new StringBuilder(text);
                            // 02:00:00
                            int timeLen = len - text.lastIndexOf('T') - 1;
                            if (timeLen <= 8) {
                                sb.append(".000");
                            }
                            sb.append('Z');
                            text = sb.toString();

                            try {
                                return new SimpleDateFormat(DATE_FORMAT_ISO8601_Z).parse(text);
                            } catch (ParseException ignore) {
                            }
                        }
                    }
                }
            } else {
                try {
                    return new SimpleDateFormat(DATE_FORMAT_RFC1123).parse(text);
                } catch (ParseException ignore) {
                }
            }

            // 尝试解析为数字
            try {
                offset = Long.parseLong(text);
            } catch (NumberFormatException ignore) {
            }
        }

        if (obj instanceof Number) {
            offset = ((Number) obj).longValue();
        }

        if (offset <= 0) {
            throw newClassCastException(obj, Date.class);
        }

        return new Date(offset);
    }

    /**
     * 判断给定的日期字符串中是否包含时区
     */
    private static boolean hasTimeZone(String date) {
        int len = date.length();
        if (len >= 6) {
            // +hh:mm
            char c = date.charAt(len - 6);
            if ('+' == c || '-' == c) {
                return true;
            }

            // +hhmm
            c = date.charAt(len - 5);
            if ('+' == c || '-' == c) {
                return true;
            }

            c = date.charAt(len - 3);
            if ('+' == c || '-' == c) {
                return true;
            }
        }
        return false;
    }

    private static ClassCastException newClassCastException(Object obj, Class<?> target) {
        throw new ClassCastException((obj != null ? obj.getClass().getName() : null) + "(" + obj + ")" + " cannot be cast to " + target.getName());
    }

    private Castor() {
    }
}
